﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.IsolatedStorage;
using System.Windows;
using System.Runtime.Serialization;
using CannedHeater.Model;
using CannedHeater.ViewModel.DataContainers;


namespace CannedHeater.ViewModel
{
    /// <summary>
    /// ViewModel for StatisticsView
    /// </summary>
    public class StatisticsViewModel : INotifyPropertyChanged
    {
        #region Locally used variables
        /// <summary>
        /// The interval in minutes at which the history should be cleaned
        /// </summary>
        private const int CLEAN_INTERVAL = 3600; // Every hour (or on app launch)

        /// <summary>
        /// The maximum amount of history to keep in days
        /// </summary>
        private const int MAX_HISTORY = 30;

        /// <summary>
        /// Isolated storage dictionary
        /// </summary>
        private IsolatedStorageSettings _iss = IsolatedStorageSettings.ApplicationSettings;

        /// <summary>
        /// Isolated storage file
        /// </summary>
        private IsolatedStorageFile _isf = IsolatedStorageFile.GetUserStoreForApplication();

        /// <summary>
        /// The last time at which data has been cleaned up
        /// </summary>
        private DateTime _lastDataClean = DateTime.MinValue; // Initially never

        #endregion

        #region Events
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion

        #region Properties
        private DataFetcher _df;
        /// <summary>
        /// The DataFetcher used for fetching data from pachube
        /// </summary>
        public DataFetcher DF
        {
            get { return _df; }
            private set
            {
                _df = value;

                // Update other DataFetcher related properties
                LatestData.AirFlow = _df.DH.AirFlow;
                LatestData.IndoorTemp = _df.DH.IndoorTemp;
                LatestData.OutdoorTemp = _df.DH.OutdoorTemp;
                LatestData.SolarHeaterTemp = _df.DH.SolarHeaterTemp;

                _df.PropertyChanged += new PropertyChangedEventHandler(_df_PropertyChanged);
                _df.DH.PropertyChanged += new PropertyChangedEventHandler(DH_PropertyChanged);
                RaisePropertyChanged("DF");
            }
        }

        #region Extra properties from DataFetcher for bindings

        private StatusPanelData _latestData = new StatusPanelData();
        /// <summary>
        /// Latest data from pachube
        /// </summary>
        public StatusPanelData LatestData
        {
            get { return _latestData; }
        }

        #endregion

        /// <summary>
        /// The view model for the graph component inside the StatisticsView
        /// </summary>
        public GraphViewModel GVM { get; private set; }
        #endregion

        #region DataFetcher property changes
        /// <summary>
        /// Handles changes in DataFetcher properties, copies data to viewmodel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _df_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "DH")
            {
                LatestData.AirFlow = DF.DH.AirFlow;
                LatestData.IndoorTemp = DF.DH.IndoorTemp;
                LatestData.OutdoorTemp = DF.DH.OutdoorTemp;
                LatestData.SolarHeaterTemp = DF.DH.SolarHeaterTemp;
            }
        }

        /// <summary>
        /// Handles changes in DataFetcher property DH, copies data to viewmodel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DH_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "AirFlow":
                    LatestData.AirFlow = DF.DH.AirFlow;
                    break;
                case "IndoorTemp":
                    LatestData.IndoorTemp = DF.DH.IndoorTemp;
                    break;
                case "OutdoorTemp":
                    LatestData.OutdoorTemp = DF.DH.OutdoorTemp;
                    break;
                case "SolarHeaterTemp":
                    LatestData.SolarHeaterTemp = DF.DH.SolarHeaterTemp;
                    break;
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Creates new statistics viewmodel
        /// </summary>
        public StatisticsViewModel()
        {
            GVM = new GraphViewModel();

            // The way the SVM is constructed seems to ensure that the constructor is only called once
            // Thus, adding these eventhandlers should be safe (not added twice)
            HostEvents.ClosingApp += new HostEvents.ClosingAppHandler(HostEvents_ClosingApp);
            HostEvents.DeactivatingApp += new HostEvents.DeactivatingAppHandler(HostEvents_DeactivatingApp);
        }
        #endregion


        /// <summary>
        /// Loads data from isolated storage,
        /// and starts an async request to fetch new data.
        /// Should only be called once, after initialization
        /// </summary>
        public void LoadData()
        {
            // If data is found in isolated storage
            if (_iss.Contains("PachubeDataLastFetched") && _iss.Contains("PachubeDataFetchTimes"))
            {
                // Restore the old datafetcher with data from isolated storage
                // Safe to call LoadHistoryFromStorage() here, since we always save DataHistory when we save PachubeDataLastFetched and PachubeDataFetchTimes
                DF = new DataFetcher(LoadHistoryFromStorage(),
                                     (DateTime)_iss["PachubeDataLastFetched"],
                                     (List<DateTime>)_iss["PachubeDataFetchTimes"]);

                // Set the view model for the graph view
                GVM.DF = _df;

                // Starts a new async request to fetch new data from pachube (since last run)
                _df.DataFetchCompleted += new DataFetcher.DataFetchCompletedEventHandler(_df_DataFirstFetchCompleted);
                _df.FetchData();
            }
            else
            {

                DF = new DataFetcher();
                _df.DataFetchCompleted += new DataFetcher.DataFetchCompletedEventHandler(_df_DataFirstFetchCompleted);
                _df.FetchData();
            }

            // Start autofetching (is delayed in the model, so no conflict with the async request started above)
            _df.AutoFetch = true;
        }

        /// <summary>
        /// Handles the data fetched after the initial fetch
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void _df_DataFirstFetchCompleted(object sender, DataFetcher.DataFetchCompletedEventArgs args)
        {
            // Should only use this handler once
            _df.DataFetchCompleted -= new DataFetcher.DataFetchCompletedEventHandler(_df_DataFirstFetchCompleted);

            if (args.Status == DataFetcher.DataFetchCompletedEventArgs.StatusType.Success)
            {
                // On success, clean data and save it
                _df.Clean(TimeSpan.FromDays(MAX_HISTORY));
                _lastDataClean = DateTime.Now;
                // If viewmodel of GraphView has not been set yet, set it now. (Happens here if no previous data was loaded in LoadData())
                // No reason to set again if already set, will probably just cause a reload of the graph, which might decrease performance
                if ( GVM.DF != _df ) GVM.DF = _df; 
                SaveHistoryToStorage();
            }
            else
            {
                // Show error (only shown on first fetch - auto fetch is silent)
                MessageBox.Show(args.Msg);
            }

            // Successive requests should have a different handler
            _df.DataFetchCompleted += new DataFetcher.DataFetchCompletedEventHandler(_df_DataSuccessiveFetchCompleted);
        }

        /// <summary>
        /// Handles events for data that has been fetched automatically
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void _df_DataSuccessiveFetchCompleted(object sender, DataFetcher.DataFetchCompletedEventArgs args)
        {
            if (args.Status == DataFetcher.DataFetchCompletedEventArgs.StatusType.Success)
            {
                // Clean if not done for CLEAN_INTERVAL time
                if ( _lastDataClean < DateTime.Now.Subtract(TimeSpan.FromMinutes(CLEAN_INTERVAL)))
                {
                    _lastDataClean = DateTime.Now;
                    _df.Clean(TimeSpan.FromDays(MAX_HISTORY));
                }
                SaveHistoryToStorage(); // Always save data (just in case)
            }
        }

        /// <summary>
        /// Loads data history from isolated storage
        /// </summary>
        /// <returns>The DataHistory object loaded from isolated storage</returns>
        /// <remarks>Precondition: DataHistory does exist in isolated storage</remarks>
        private DataHistory LoadHistoryFromStorage()
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof(DataHistory));
            using (IsolatedStorageFileStream stream = _isf.OpenFile("DataHistory", System.IO.FileMode.Open))
            {
                return (DataHistory)serializer.ReadObject(stream);
            }
        }

        /// <summary>
        /// Saves history to isolated storage
        /// </summary>
        private void SaveHistoryToStorage()
        {
            if (_df == null) return;
            
            // DataHistory
            DataContractSerializer serializer = new DataContractSerializer(typeof (DataHistory));
            using ( IsolatedStorageFileStream stream = _isf.CreateFile("DataHistory"))
            {
                serializer.WriteObject(stream, _df.DH);
            }

            // Other important parameters
            if (_iss.Contains("PachubeDataLastFetched"))
                _iss["PachubeDataLastFetched"] = _df.LastFetched;
            else
                _iss.Add("PachubeDataLastFetched", _df.LastFetched);
            if (_iss.Contains("PachubeDataFetchTimes"))
                _iss["PachubeDataFetchTimes"] = _df.FetchedTimes;
            else
                _iss.Add("PachubeDataFetchTimes", _df.FetchedTimes);

            _iss.Save();
        }

        #region Cleaning history
        private void HostEvents_DeactivatingApp(object sender, EventArgs args)
        {
            CleanAndSave();
        }

        private void HostEvents_ClosingApp(object sender, EventArgs args)
        {
            CleanAndSave();
        }

        /// <summary>
        /// Clean and save data
        /// </summary>
        private void CleanAndSave()
        {
            _df.Clean(TimeSpan.FromDays(MAX_HISTORY)); // Clean the data in the DataFetcher
            SaveHistoryToStorage();
        }
        #endregion


        #region Helpers
        public void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
        #endregion
    }
}
